package com.ph.finder.client.finder.ui;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.geocode.Geocoder;
import com.google.gwt.maps.client.geocode.LocationCallback;
import com.google.gwt.maps.client.geocode.Placemark;
import com.google.gwt.maps.client.geom.LatLng;
import com.google.gwt.maps.client.geom.LatLngBounds;
import com.google.gwt.maps.client.geom.Point;
import com.google.gwt.maps.client.geom.Size;
import com.google.gwt.maps.client.overlay.Icon;
import com.google.gwt.maps.client.overlay.Marker;
import com.google.gwt.maps.client.overlay.Overlay;
import com.google.gwt.maps.client.overlay.Polygon;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.ph.finder.client.AddressInfo;
import com.ph.finder.client.PrecinctInfo;
import com.ph.finder.client.Util;
import com.ph.finder.client.ui.UIUtils;

public class PrecinctSearchLPanel extends Composite {

	private static PrecinctSearchLPanelUiBinder uiBinder = GWT
			.create(PrecinctSearchLPanelUiBinder.class);

	interface PrecinctSearchLPanelUiBinder extends
			UiBinder<Widget, PrecinctSearchLPanel> {
	}

	@UiField
	InlineLabel sellText;

	@UiField
	InlineLabel mappingAddress;

	@UiField
	InlineLabel voterCount;

	@UiField
	InlineLabel clusterCount;

	@UiField
	HTMLPanel loadingPane;

	@UiField
	Image spinnerImage;

	@UiField
	InlineLabel loadingLabel;

	@UiField
	Label errorLabel;

	private FindPrecinctRootPage ctr;
	private final MapWidget maps;

	private final PrecinctInfo precinctInfo;

	public PrecinctSearchLPanel(FindPrecinctRootPage findPrecinctRootPage,
			PrecinctInfo precinctInfo, MapWidget maps) {
		this.ctr = findPrecinctRootPage;
		this.precinctInfo = precinctInfo;
		this.maps = maps;
		initWidget(uiBinder.createAndBindUi(this));

		AddressInfo address = precinctInfo.getAddress();
		if (address.getLat() == 0) {  // No Map location
			sellText.setText("We have found the address of your precinct.  " +
					"Unfortunately, no voluteers have placed it on the map.  " +
					"We will try to find it automatically.");

			// Start smart search
			startSmartSearch();
			Util.track("/FindYourPrecinct/03-NoData");
		} else {
			sellText.setText("We have found your precinct.  " +
					"Thanks to voluteers who placed your precinct on the map.  " +
					"Care to help out too?");

			// Jump to the location directly
			Marker marker = getPrecinctLocationMarker(LatLng.newInstance(
					address.getLat(), address.getLng()));
			maps.addOverlay(marker);
			maps.setCenter(marker.getLatLng());
			maps.setZoomLevel(17);
			Util.track("/FindYourPrecinct/03-VolunteerMapped");
		}
		mappingAddress.setText(address.getDisplayAddress());
		voterCount.setText(NumberFormat.getDecimalFormat().format(address.getVotersCount()));
		clusterCount.setText(NumberFormat.getDecimalFormat().format(address.getClusterCount()));

		Util.track("/FindYourPrecinct/00-Start");
	}

	private void startSmartSearch() {
		loadingPane.setVisible(true);
		spinnerImage.setVisible(true);
		loadingLabel.setText("Attempting to Auto-locate the address...");

		Geocoder coder = new Geocoder();
		final String searchAddress = precinctInfo.getCity() + "," +
				precinctInfo.getProvince() + ", Philippines";
		coder.setBaseCountryCode("ph");
		coder.setViewport(LatLngBounds.newInstance(
				LatLng.newInstance(4.127285, 114.609375 ),
				LatLng.newInstance(21.207459, 127.705078 )));
		coder.getLocations(cleanAddress(searchAddress), new SmartLocationCallback(maps,
				cleanAddress(precinctInfo.getAddress().getDisplayAddress()), null, null, new FinishSmartMap() {
			@Override
			public void done(LatLng loc, int Acc) {
				spinnerImage.setVisible(false);
				loadingLabel.setText("");
				if (loc != null) {
					while (maps.getZoomLevel() < 17)
						maps.zoomIn();
					maps.panTo(loc);
					Util.track("/FindYourPrecinct/05-SmartSearch");
				}
			}
		}, 4, errorLabel));
	}

	private static interface FinishSmartMap {

			void done(LatLng loc, int Acc);
	}

	private String cleanAddress(String searchAddress) {
		return searchAddress.replace("Elem.", "Elementary");
	}

	static Marker getPrecinctLocationMarker(LatLng point) {
		Marker marker = new Marker(point);
		marker.setDraggingEnabled(false);
		Icon precinctIcon = marker.getIcon();
		precinctIcon.setImageURL("http://chart.apis.google.com/chart?chst=d_map_xpin_icon&chld=pin_sright|civic-building|C3593C");
		precinctIcon.setShadowURL("http://chart.apis.google.com/chart?chst=d_map_xpin_shadow&chld=pin_sright|civic-building|C3593C");
		precinctIcon.setIconSize(Size.newInstance(23, 33));
		precinctIcon.setShadowSize(Size.newInstance(42, 36));
		precinctIcon.setIconAnchor(Point.newInstance(0, 33));
		return marker;
	}

	static public class SmartLocationCallback implements LocationCallback {

		private final MapWidget maps2;
		private final Placemark bestLocation;
		private final LatLngBounds bounds;
		private final FinishSmartMap done;
		private final String nextAddress;
		private final int iteration;
		private final Label errorLabel2;

		public SmartLocationCallback(MapWidget maps, String nextAddress,
				Placemark bestLocation,
				LatLngBounds bounds, FinishSmartMap done, int iteration,
				Label errorLabel) {
					maps2 = maps;
					this.nextAddress = nextAddress;
					this.bestLocation = bestLocation;
					this.bounds = bounds;
					this.done = done;
					this.iteration = iteration;
					errorLabel2 = errorLabel;
		}

		@Override
		public void onSuccess(JsArray<Placemark> locations) {
			int bestLocation = -1;
			Log.debug("Found:" + locations.length());
			for (int i = 0; i < locations.length(); i++) {
				Log.debug("Pre-Acc:" + locations.get(i).getAccuracy());
				Placemark loc = locations.get(i);
				// Bounds Check
				if (bounds != null) {
					if (bounds.getSouthWest().getLatitude() >= loc.getPoint().getLatitude() ||
							bounds.getSouthWest().getLongitude() >= loc.getPoint().getLongitude() ||
							bounds.getNorthEast().getLatitude() < loc.getPoint().getLatitude() ||
							bounds.getNorthEast().getLongitude() < loc.getPoint().getLongitude()) {
						Log.debug("Out of Bounds.");
						continue;
					}
				}
				if (bestLocation == -1 || loc.getAccuracy() >
						locations.get(bestLocation).getAccuracy()) {
					bestLocation = i;
				}
			}
			final LatLngBounds finalBounds;
			if (bestLocation != -1) {
				Placemark best = locations.get(bestLocation);
				double lat = best.getPoint().getLatitude();
				double lng = best.getPoint().getLongitude();
				if (bounds == null) {
					maps2.panTo(best.getPoint());
					Timer zoomer = new Timer() {
						@Override
						public void run() {
							if (maps2.getZoomLevel() < 14) {
								maps2.zoomIn();maps2.zoomIn();
							}
							if (maps2.getZoomLevel() < 14) {
								this.schedule(1000);
							}
						}
					};
					zoomer.schedule(1000);
				}
				double off = 0.3;
				if (best.getAccuracy() == 4) {
					off = 0.2;
				} else if (best.getAccuracy() == 5) {
					off = 0.1;
				} else if (best.getAccuracy() == 6) {
					off = 0.05;
				} else if (best.getAccuracy() == 7) {
					off = 0.02;
				} else if (best.getAccuracy() == 8) {
					off = 0.008;
				} else if (best.getAccuracy() == 9) {
					off = 0.004;
				}
				for (int i = 0; i < 3; i++) {
					double offNew = (off / 6) * (6 - i);
					double transNew = (.6 / 5) * (5 - i);
					Overlay ov1 = new Polygon(
							new LatLng[] {
									LatLng.newInstance(lat - offNew, lng - offNew),
									LatLng.newInstance(lat - offNew, lng + offNew),
									LatLng.newInstance(lat + offNew, lng + offNew),
									LatLng.newInstance(lat + offNew, lng - offNew),
									LatLng.newInstance(lat - offNew, lng - offNew)
							}
							, "#FFF", 2, transNew, "", 0);
					maps2.addOverlay(ov1);
				}
				if (bounds == null) {
					finalBounds = LatLngBounds.newInstance(
							LatLng.newInstance(lat - off, lng - off),
							LatLng.newInstance(lat + off, lng + off)
							);
				} else {
					finalBounds = bounds;
				}
				if (best.getAccuracy() >= 6) {
					// Plot a point too if its close enough
					Marker guess = getPrecinctLocationMarker(best.getPoint());
					maps2.addOverlay(guess);
					done.done(best.getPoint(), 18);
					return;
				}
				Log.debug("Acc:" + best.getAccuracy());
			} else {
				finalBounds = bounds;
			}
			trySearchingAgain(finalBounds);
		}

		@Override
		public void onFailure(int statusCode) {
			if (statusCode < 600 || statusCode > 604) {
				// Fatal Codes -- don't retry
				Log.debug("Failed FATALLY.  Status:" + statusCode);
				UIUtils.error(errorLabel2, "Failed.");
				done.done(null, 0);
				return;
			}
			if (bounds == null) {
				// Try without the last specifier
				Log.debug("Not even the city was found. :(");
				UIUtils.error(errorLabel2, "Failed in looking for the city/municipality.");
			} else {
				// Search but with one less
				trySearchingAgain(bounds);
			}
		}

		public void trySearchingAgain(final LatLngBounds finalBounds) {
			if (iteration == 0) {
				Log.debug("Ran out of iterations.");
				UIUtils.error(errorLabel2, "Not found. Please improve the map to have it.");
			}
			// Try with the nextAddress (remove last component)
			String newAddress = nextAddress;
			if (bounds != null) {
				if (newAddress.lastIndexOf(",") != -1) {
					newAddress = newAddress.substring(0, newAddress.lastIndexOf(","));
				} else {
					done.done(null, 0);
					Log.debug("Ran out of commas.");
					UIUtils.error(errorLabel2, "Not found. Please improve the map to have it.");
					return;
				}
			}
			final String finalAddress = newAddress;
			Log.debug("Running: " + newAddress);
			Timer time = new Timer() {

				@Override
				public void run() {
					Geocoder coder = new Geocoder();
					coder.getLocations(finalAddress, new SmartLocationCallback(maps2, finalAddress,
							SmartLocationCallback.this.bestLocation, finalBounds, done,
							iteration - 1, errorLabel2));
				}
			};
			time.schedule(1000);
		}
	}

}
